package com.yahoo.astra.fl.charts.axes
{
	import com.yahoo.astra.fl.charts.series.ISeries;
	import com.yahoo.astra.fl.charts.CartesianChart;
	import fl.core.UIComponent;
	import flash.text.TextFormat;	
	/**
	 * An axis type representing a set of categories.
	 * 
	 * @author Josh Tynjala
	 */
	public class CategoryAxis extends BaseAxis implements IAxis, IClusteringAxis
	{
		
	//--------------------------------------
	//  Constructor
	//--------------------------------------
	
		/**
		 * Constructor.
		 */
		public function CategoryAxis()
		{
		}
		
	//--------------------------------------
	//  Properties
	//--------------------------------------
		
		/**
		 * @private
		 * Used to determine the position of an item based on a value.
		 */
		protected var categorySize:Number = 0;
		
		/**
		 * @private
		 * Storage for the categoryNames property.
		 */
		private var _categoryNames:Array = [];
		
		/**
		 * @private
		 * Indicates whether the category labels are user-defined or generated by the axis.
		 */
		private var _categoryNamesSetByUser:Boolean = false;
		
		/**
		 * The category labels to display along the axis.
		 */
		public function get categoryNames():Array
		{
			return this._categoryNames;
		}
		
		/**
		 * @private
		 */
		public function set categoryNames(value:Array):void
		{
			this._categoryNamesSetByUser = value != null && value.length > 0;
			if(!this._categoryNamesSetByUser)
			{
				this._categoryNames = [];
			}
			else
			{
				//ensure that all category names are strings
				this._categoryNames = getCategoryNames(value);
			}
		}
		
		/**
		 * @inheritDoc
		 */
		public function get clusterCount():int
		{
			return this.categoryNames.length;
		}

		/**
		 * @private
		 */
		private var _numLabels:Number;
		
		/**
		 * @private
		 */		
		private var _numLabelsSetByUser:Boolean = false;

		/**
		 * @inheritDoc
		 */
		public function get numLabels():Number
		{
			return _numLabels;
		}
		
		/**
		 * @private (setter)
		 */
		public function set numLabels(value:Number):void
		{
			if(_numLabelsSetByUser) return;
			_numLabels = value;
			_numLabelsSetByUser = true;
		}		
		
		/**
		 * @private
		 */
		private var _majorUnit:Number = 1; 
		
		/**
		 * @private 
		 * Holds value for calculateCategoryCount
		 */
		private var _calculateCategoryCount:Boolean = false;
		
		/**
		 * Indicates whether or not to calculate the number of categories (ticks and labels) 
		 * when there is not enough room to display all labels on the axis. If set to true, the axis 
		 * will determine the number of categories to plot. If not, all categories will be plotted. 
		 */
		public function get calculateCategoryCount():Boolean
		{
			return _calculateCategoryCount;
		}
		
		/**
		 * @private (setter)
		 */
		public function set calculateCategoryCount(value:Boolean):void
		{
			_calculateCategoryCount = value;
		}
		
	//--------------------------------------
	//  Public Methods
	//--------------------------------------
		
		/**
		 * @inheritDoc
		 */
		public function valueToLocal(value:Object):Number
		{
			if(value === null)
			{
				return NaN;
			}
			
			var index:int = this.categoryNames.indexOf(value.toString());
			if(index >= 0)
			{
				var position:int = this.categorySize * index + (this.categorySize / 2);
				return position;
			}
			return NaN;
		}
	
		/**
		 * @inheritDoc
		 */
		public function updateScale():void
		{
			if(!this._categoryNamesSetByUser)
			{
				this.autoDetectCategories(this.dataProvider);
			}
			this.calculateCategorySize();
		}

		/**
		 * @inheritDoc
		 */
		override public function getMaxLabel():String
		{
			var categoryCount:int = this.categoryNames.length;
			var maxLength:Number = 0;
			var currentLength:Number;
			var maxString:String = "x";

			for(var i:int = 0; i < categoryCount; i++)
			{
				currentLength = (this.categoryNames[i].toString()).length; 

				if(currentLength > maxLength)
				{
					maxLength = currentLength;
					maxString = this.categoryNames[i];
				}
			}			
			
			return this.valueToLabel(maxString) as String;	
		}

	//--------------------------------------
	//  Private Methods
	//--------------------------------------
	
		/**
		 * @private
		 * Update the labels by adding or removing some, setting the text, etc.
		 */
		private function autoDetectCategories(data:Array):void
		{
			var uniqueCategoryNames:Array = [];
			var seriesCount:int = data.length;
			for(var i:int = 0; i < seriesCount; i++)
			{
				var series:ISeries = data[i] as ISeries;
				if(!series)
				{
					continue;
				}
				
				var seriesLength:int = series.length;
				for(var j:int = 0; j < seriesLength; j++)
				{
					var category:Object = this.chart.itemToAxisValue(series, j, this);
					
					//names must be unique
					if(uniqueCategoryNames.indexOf(category) < 0)
					{
						uniqueCategoryNames.push(category);
					}
				}
			}
			this._categoryNames = getCategoryNames(uniqueCategoryNames.concat());
		}
		
		/**
		 * @private
		 * Determines the amount of space provided to each category.
		 */
		private function calculateCategorySize():void
		{
			var categoryCount:int = this.categoryNames.length;
			this.categorySize = this.renderer.length;
			if(categoryCount > 0)
			{
				this.categorySize /= categoryCount;
			}
			
			//If the number of labels will not fit on the axis or the user has specified the number of labels to
			//display, calculate the major unit. 
			var maxLabelSize:Number = (this.chart as CartesianChart).horizontalAxis == this ? this.labelData.maxLabelWidth : this.labelData.maxLabelHeight;
			if((this.categorySize < maxLabelSize && this.calculateCategoryCount) || (this._numLabelsSetByUser && this.numLabels != categoryCount))
			{
				this.calculateMajorUnit();
				(this.renderer as ICartesianAxisRenderer).majorUnitSetByUser = false;
			} 
			else
			{
				(this.renderer as ICartesianAxisRenderer).majorUnitSetByUser = true;
			}
			this.updateAxisRenderer();
		}
		
		/**
		 * @private
		 * Calculates which labels to skip if they will not all fit on the axis.
		 */
		private function calculateMajorUnit():void
		{
			var overflow:Number = 0;
			var rotation:Number = (this.renderer as UIComponent).getStyle("labelRotation") as Number;
			var chart:CartesianChart = this.chart as CartesianChart;
			var maxLabelSize:Number;
			if(chart.horizontalAxis == this)
			{
				maxLabelSize = this.labelData.maxLabelWidth;
				if(rotation >= 0)
				{
					if(!isNaN(this.labelData.rightLabelOffset)) overflow += this.labelData.rightLabelOffset as Number;
				}
				if(rotation <= 0)
				{
					if(!isNaN(this.labelData.leftLabelOffset)) overflow += this.labelData.leftLabelOffset as Number;
				}			
			}
			else
			{
				maxLabelSize = this.labelData.maxLabelHeight;
				if(!isNaN(this.labelData.topLabelOffset)) overflow = this.labelData.topLabelOffset as Number;				
			}
			var labelSpacing:Number = this.labelSpacing; 
			maxLabelSize += (labelSpacing*2);
			var categoryCount:int = this.categoryNames.length;

			
			var maxNumLabels:Number = (this.renderer.length + overflow)/maxLabelSize;
			
			//If the user specified number of labels to display, attempt to show the correct number.
			if(this._numLabelsSetByUser)
			{
				maxNumLabels = Math.min(maxNumLabels, this.numLabels);
			}
			var ratio:Number = overflow/this.renderer.length;
			var overflowOffset:Number = ratio*this.categoryNames.length;
			if(isNaN(overflowOffset)) overflowOffset = 0;		
			var tempMajorUnit:Number = Math.round((this.categoryNames.length+overflowOffset)/maxNumLabels);
			this._majorUnit = tempMajorUnit;				
		}
		
		/**
		 * @private 
		 * Ensures all values in an array are string values
		 */
		private function getCategoryNames(value:Array):Array
		{
			var names:Array = [];
			if(value != null && value.length > 0)
			{
				for(var i:int = 0; i < value.length; i++)
				{
					names.push(value[i].toString());
				}
			}
			return names;
		}

		/**
		 * @private
		 */
		protected function updateAxisRenderer():void
		{
			var ticks:Array = [];
			var categoryCount:int = this.categoryNames.length;
			if(this.reverse) this.categoryNames = this.categoryNames.reverse();
			var currentCat:int = 0;
			while(currentCat < categoryCount && !isNaN(categoryCount))
			{
				var category:String = this.categoryNames[currentCat];
				var position:Number = this.valueToLocal(category);
				var label:String = this.valueToLabel(category);
				var axisData:AxisData = new AxisData(position, category, label);
				
				if(currentCat % this._majorUnit == 0) ticks.push(axisData);
				currentCat += 1;
			}
			
			this.renderer.ticks = ticks;
			this.renderer.minorTicks = [];				
		}	
		
		/**
		 * @private
		 */
		override protected function parseDataProvider():void
		{
			if(!this._categoryNamesSetByUser) this.autoDetectCategories(this.dataProvider);
			var labelData:Object = getLabelData();			
			if(ICartesianAxisRenderer(this.renderer).orientation == AxisOrientation.HORIZONTAL)
			{
				labelData.leftLabelOffset /= 2;
				labelData.rightLabelOffset /= 2;
			}
			else
			{
				labelData.topLabelOffset /= 2;
				labelData.bottomLabelOffset /=2;
			}
			for(var i:String in labelData)
			{	
				this.labelData[i] = labelData[i];
			}
		}			
	}
}